#include <QApplication>
#include <QCursor>
#include <QDir>
#include <QFileDialog>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
#include <QTreeView>

#include "cgui.h"
#include "src/caddsectiondialog.h"
#include "src/caddfielddialog.h"
#include "src/centerkeydialog.h"
#include "src/constants.h"
#include "src/creplicationform.h"
#include "src/csyncprocessdialog.h"
#include "src/caboutdialog.h"

#ifdef IS_MOBILE
    #include "src/mobile/stackedmainwindow.h"
#else
    #include "src/desktop/mainwindow.h"
#endif // IS_MOBILE

CGui::CGui(QSettings *set) :
    m_current(0), m_set(set), m_alreadyLoaded(false)
{

#ifdef IS_MOBILE
/*    m_sectionsWnd = new CSectionsWindow();
    m_entriesWnd = new CEntriesWindow();
    m_browserWnd = new CBrowserWindow();

    m_treeView = m_sectionsWnd->treeView();
    m_tableView = m_entriesWnd->tableView();
    m_textBrowser = m_browserWnd->textBrowser();

    m_entriesWnd->hide();
    m_browserWnd->hide();*/

    m_wnd = new StackedMainWindow(m_set);
    m_treeView = m_wnd->treeView();
    m_tableView = m_wnd->tableView();
    m_textBrowser = m_wnd->textBrowser();

    //connect(m_wnd, SIGNAL(closed()), this, SLOT(exit()));
#else
    m_wnd = new MainWindow(m_set);
    m_treeView = m_wnd->treeView();
    m_tableView = m_wnd->tableView();
    m_textBrowser = m_wnd->textBrowser();

    //connect(m_wnd, SIGNAL(closed()), this, SLOT(exit()));
    connect(m_wnd->vSplitter(), SIGNAL(splitterMoved(int,int)), m_tableView, SLOT(resizeColumns()));
#endif // IS_MOBILE

    m_db = new CDatabase(QString());

    m_model = new CTreeModel(*m_db, this);
    connect(m_model, SIGNAL(update(bool)), this, SLOT(update(bool)));

    m_entries = new CEntriesModel(*m_db, this);
    connect(m_entries, SIGNAL(modelReset()), this, SLOT(resizeColumns()));

#ifndef IS_MOBILE
//    connect(m_entries, SIGNAL(modelReset()), this, SLOT(loadEntry()));
#endif
    
    m_replicator = new CReplicator(m_db);
    connect(m_replicator, SIGNAL(received(int)), this, SLOT(syncFinished(int)));

    m_outGen = new COutputGenerator(m_textBrowser, ":/html");

    connect(m_treeView, SIGNAL(customContextMenuRequested(const QPoint &)),
            this, SLOT(showTreeContextMenu(const QPoint &)));
    connect(m_tableView, SIGNAL(customContextMenuRequested(const QPoint &)),
            this, SLOT(showTableContextMenu(const QPoint &)));
    connect(m_textBrowser, SIGNAL(customContextMenuRequested(const QPoint &)),
            this, SLOT(showBrowserContextMenu(const QPoint&)));

    connect(m_textBrowser, SIGNAL(editField(int)), this, SLOT(editField(int)));
    connect(m_textBrowser, SIGNAL(deleteField(int)), this, SLOT(deleteField(int)));
    connect(m_textBrowser, SIGNAL(saveImage(QPixmap)), this, SLOT(saveImage(QPixmap)));
    connect(m_textBrowser, SIGNAL(saveFile(int)), this, SLOT(saveFile(int)));

#ifndef IS_MOBILE
    connect(m_treeView, SIGNAL(pressed(const QModelIndex&)), this, SLOT(loadSubsection(const QModelIndex&)));
#endif

    connect(m_treeView, SIGNAL(activated(const QModelIndex&)), this, SLOT(loadSubsection(const QModelIndex&)));
    connect(m_treeView, SIGNAL(collapsed(QModelIndex)), m_treeView, SLOT(expand(QModelIndex)));

#ifdef IS_MOBILE
    connect(m_tableView, SIGNAL(pressed(const QModelIndex&)), this, SLOT(setCurrentEntry(const QModelIndex&)));
#else
    connect(m_tableView, SIGNAL(pressed(const QModelIndex&)), this, SLOT(loadEntry(const QModelIndex&)));
#endif
    connect(m_tableView, SIGNAL(activated(const QModelIndex&)), this, SLOT(loadEntry(const QModelIndex&)));

    createActions();
}

CGui::~CGui()
{
    m_treeView->setModel(0);
    delete m_db;
#ifdef IS_MOBILE
/*    delete m_sectionsWnd;
    delete m_entriesWnd;
    delete m_browserWnd;*/
    delete m_wnd;
#else
    delete m_wnd;
#endif // IS_MOBILE
    delete m_model;

    delete m_replicator;
}

void CGui::update(bool needEntries)
{
    debug("update()");

    QString current;
    if(m_current)
        current = m_current->id();

    m_current = 0;

    if(needEntries)
        m_entries->refill();

    loadEntry(current);

    m_treeView->expandAll();
}

void CGui::show()
{
    m_current = 0;

    m_treeView->setModel(m_model);
    m_treeView->expandAll();

    m_tableView->setModel(m_entries);

    m_model->refill();

    m_wnd->setEnabled(true);

#ifdef IS_MOBILE
    m_wnd->showSections();
    m_wnd->showFullScreen();
#else
    if(m_model->rowCount())
        loadSubsection(m_model->index(0, 0));
    else
    {
        m_entries->setId("");
        m_textBrowser->setHtml(m_outGen->startScreen());
    }
    m_wnd->show();
#endif // IS_MOBILE

    resizeColumns();
}

QString CGui::askKey()
{
    if(!QCA::getProperty("key").isValid())
    {
        CEnterKeyDialog dialog(*m_db);

        if(dialog.exec()==QDialog::Rejected)
        {
            return QString();
        }

        return dialog.key();
    }

    return QString::fromUtf8(QCA::getProperty("key").toByteArray());
}

void CGui::loadDatabase(QString filename)
{
    m_alreadyLoaded = true;
    if(m_db->setFilename(filename))
        QCA::setProperty("md5", m_db->getKeyMd5());
    else
    {
        critical(tr("Error"), tr("Can't open database"));
        close();
        return;
    }

    m_set->setValue("database", filename);

    if(!QCA::getProperty("key").isValid())
    {
        QString key = askKey();
        if(key.isNull())
        {
            //close();
            m_wnd->show();
            m_db->close();
            return;
        }
        QCA::setProperty("key", key.toUtf8());
    }

    bool supported = false;
    foreach(QCA::Provider *p, QCA::providers())
    {
        QByteArray cipher = QString(QCA::getProperty("cipher").toString() + "-cbc-pkcs7").toLatin1();
        supported = supported ||
                    QCA::isSupported(cipher.data(), p->name());
    }

    if(!supported)
    {
        critical(tr("Error"), tr("Database cipher doesn't supported. Check app's libs."));
        close();
        return;
    }

    m_wnd->setTitle(m_db->filename());

    show();
}

void CGui::createDatabase(QString filename, QString cipher)
{
    m_cipher = cipher;

    QCA::setProperty("key", QVariant());
    m_db->close();

    QString key = askKey();
    QCA::setProperty("key", key.toUtf8());
    if(m_db->createDatabase(filename, cipher, key))
        loadDatabase(filename);
    else
    {
        critical(tr("Error"), tr("Can't create database"));
        close();
        return;
    }
}

void CGui::critical(QString caption, QString text)
{
    QMessageBox::critical(m_wnd, caption, text);
}

void CGui::close()
{
    debug("close");
    QTimer::singleShot(0, m_wnd, SLOT(close()));

}

QWidget* CGui::getWindow()
{
    return m_wnd;
}

void CGui::createActions()
{
/* Tree actions */
    m_treeActions.insert(this->AddSubsection, new QAction(QIcon(":/icons/subsection_add"), tr("Add subsection"), this));
    connect(m_treeActions.value(this->AddSubsection), SIGNAL(triggered()), this, SLOT(addSubsection()));

    m_treeActions.insert(this->EditSubsection, new QAction(QIcon(":/icons/subsection_edit"), tr("Edit subsection"), this));
    connect(m_treeActions.value(this->EditSubsection), SIGNAL(triggered()), this, SLOT(editSubsection()));

    m_treeActions.insert(this->DeleteSubsection, new QAction(QIcon(":/icons/subsection_delete"), tr("Delete subsection"), this));
    connect(m_treeActions.value(this->DeleteSubsection), SIGNAL(triggered()), this, SLOT(deleteSubsection()));
/* End of tree actions */

/* Table actions */
    m_tableActions.insert(this->AddEntry, new QAction(QIcon(":/icons/entry_add"), tr("Add entry"), this));
    connect(m_tableActions.value(this->AddEntry), SIGNAL(triggered()), this, SLOT(addEntry()));

    m_tableActions.insert(this->EditEntry, new QAction(QIcon(":/icons/entry_edit"), tr("Edit entry"), this));
    connect(m_tableActions.value(this->EditEntry), SIGNAL(triggered()), this, SLOT(editEntry()));

    m_tableActions.insert(this->DeleteEntry, new QAction(QIcon(":/icons/subsection_delete"), tr("Delete"), this));
    connect(m_tableActions.value(this->DeleteEntry), SIGNAL(triggered()), this, SLOT(deleteEntry()));
/* End of table actions */

/* Entry actions */
    m_entryActions.insert(this->AddField, new QAction(QIcon(":/icons/field_add"), tr("Add field"), this));
    connect(m_entryActions.value(this->AddField), SIGNAL(triggered()), this, SLOT(addField()));
/* End of entry actions */

/* Common actions */
    m_commonActions.insert(this->Exit, new QAction(QIcon(":/icons/exit"), tr("Exit"), this));
    connect(m_commonActions.value(this->Exit), SIGNAL(triggered()), this, SLOT(close()));

    m_commonActions.insert(this->ShowWizard, new QAction(QIcon(":/icons/database_key"), tr("Create/open another database"), this));
    connect(m_commonActions.value(this->ShowWizard), SIGNAL(triggered()), this, SLOT(showWizard()));

    m_commonActions.insert(this->Synchronize, new QAction(QIcon(":/icons/refresh"), tr("Synchronization"), this));
    connect(m_commonActions.value(this->Synchronize), SIGNAL(triggered()), this, SLOT(showSync()));

    m_commonActions.insert(this->ShowHide, new QAction(QIcon(":/icons/eye"), tr("Show/Hide"), this));
    m_commonActions.value(this->ShowHide)->setCheckable(true);
    connect(m_commonActions.value(this->ShowHide), SIGNAL(triggered()), this, SLOT(showHidePassword()));

    m_commonActions.insert(this->ShowAbout, new QAction(QIcon(":/icons/eye"), tr("About"), this));
    connect(m_commonActions.value(this->ShowAbout), SIGNAL(triggered()), this, SLOT(showAbout()));
/* End of common actions */
}

void CGui::exit()
{
    #ifdef IS_MOBILE

    #else

    #endif // IS_MOBILE

    m_set->sync();
}

void CGui::addSubsection()
{
#ifndef IS_MOBILE
    bool need_load = !m_model->rowCount();
#endif

    debug("addSubsection()");
    QString parent = m_model->itemId(m_treeView->currentIndex());

    CAddSectionDialog dialog(parent, m_model->itemName(m_treeView->currentIndex()), getWindow());
    if(dialog.exec()==QDialog::Rejected)
        return;
    m_db->addSection(dialog.map());

    m_model->refill();

#ifndef IS_MOBILE
    if(need_load)
        loadSubsection(m_model->index(0, 0));
#endif
}

void CGui::editSubsection()
{
    debug("editSubsection()");

    CTreeItem *item = m_model->item(m_treeView->currentIndex());
    if(!item)
        return;

    CAddSectionDialog dialog(item->parent()->id(), item->name().toString(), getWindow());

    dialog.setItem(item);
    if(dialog.exec()==QDialog::Rejected)
        return;

    m_db->updateSection(dialog.map(), dialog.id());

    m_model->refill();
}

void CGui::deleteSubsection()
{
    debug("deleteSubsection()");

    QString id = m_model->itemId(m_treeView->currentIndex());
    if(id.isNull())
        return;

    if(QMessageBox::question(getWindow(), tr("Question"), tr("Are you sure you want to delete this section and all subsections?"),
                          QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes)
        return;

    if(!m_db->deleteSection(id))
        return;

    m_current = 0;

    m_model->refill();
    m_entries->refill();
}

void CGui::addEntry()
{
    debug("addEntry()");

    CTreeItem *item = m_model->item(m_treeView->currentIndex());

    CAddEntryDialog dialog(item->id(), item->name().toString(), getWindow());
    if(dialog.exec()==QDialog::Rejected)
        return;

    m_db->addEntry(dialog.map());

    QString section = m_model->itemId(m_treeView->currentIndex());

    m_model->refill();
    m_entries->refill();

    m_treeView->setCurrentIndex(m_model->findIndex(QModelIndex(), section));

    resizeColumns();
}

void CGui::editEntry()
{
    debug("editEntry()");

    CTreeItem *item = m_model->item(m_treeView->currentIndex());
    
    CAddEntryDialog dialog(item->id(), item->name().toString(), getWindow());

    dialog.setEntry(m_current);
    if(dialog.exec()==QDialog::Rejected)
        return;

    m_db->updateEntry(dialog.map(), dialog.id());

    QString current;
    if(m_current)
        current = m_current->id();

    m_current = 0;

    m_entries->refill();
    //m_model->refill();

    debug("Current: " + current);

#ifndef IS_MOBILE
    loadEntry(current);
#endif
}

void CGui::deleteEntry()
{
    debug("deleteEntry()");

    if(QMessageBox::question(getWindow(), tr("Question"), tr("Are you sure you want to delete this entry?"),
                          QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes)
        return;

    if(!m_db->deleteEntry(m_current->id()))
        return;

    QString section = m_model->itemId(m_treeView->currentIndex());

    m_model->refill();
    m_entries->refill();

    m_treeView->setCurrentIndex(m_model->findIndex(QModelIndex(), section));
}

void CGui::showHidePassword()
{
    debug("showHidePassword()");
    m_entries->changeShowHide();

    QString html = m_outGen->generate(m_current, m_entries->showhide());
    m_textBrowser->setHtml(html);
}

void CGui::showTreeContextMenu(const QPoint &position)
{
    QMap<int, QAction*> map = append(&m_treeActions, &m_commonActions);

    if(m_db->isOpen())
    {
        QModelIndex idx = m_treeView->indexAt(position);

        if(!idx.isValid())
        {
            idx = m_treeView->rootIndex();
            m_treeView->setCurrentIndex(idx);
            map.remove(this->EditSubsection);
            map.remove(this->DeleteSubsection);
        }
    }
    else
    {
        map.remove(this->AddSubsection);
        map.remove(this->EditSubsection);
        map.remove(this->DeleteSubsection);
        map.remove(this->Synchronize);
    }

    QMenu::exec(map.values(), QCursor::pos());
}

void CGui::showTableContextMenu(const QPoint &position)
{
    if(m_model->itemId(m_treeView->currentIndex())==CTreeModel::rootId())
        return;

    debug("Item id: " + m_model->itemId(m_treeView->currentIndex()));

    QMap<int, QAction*> map = append(&m_tableActions, &m_commonActions);

    QModelIndex idx = m_tableView->indexAt(position);

    if(!idx.isValid())
    {
        map.remove(this->DeleteEntry);
        map.remove(this->EditEntry);
    }

    QMenu::exec(map.values(), QCursor::pos());
}

void CGui::showBrowserContextMenu(const QPoint &)
{
    if(!m_current)
        return;

    QMap<int, QAction*> map = append(&m_entryActions, &m_commonActions);

    QMenu::exec(map.values(), QCursor::pos());
}

void CGui::loadSubsection(const QModelIndex &index)
{
    debug("loadSubsection()");
    m_textBrowser->clearImages();
    m_current = 0;

    QString id = m_model->itemId(index);
    m_entries->setId(id);

    m_tableView->setColsCount(m_entries->columnCount(QModelIndex()));

    #ifdef IS_MOBILE
        m_wnd->showEntries();
    #else
        loadEntry();
    #endif

    //m_model->refill();
    //m_textBrowser->clear();
}

ActionMap CGui::append(QList<ActionMap*> list)
{
    ActionMap result;

    foreach(ActionMap *map, list)
        foreach(int key, map->keys())
            result.insert(key, map->value(key));

    return result;
}

ActionMap CGui::append(ActionMap *map1, ActionMap *map2)
{
    QList<ActionMap*> list;
    list << map1 << map2;
    return append(list);
}

void CGui::resizeColumns()
{
    int count = m_entries->columnCount();
    if(!count)
        return;

    int width = m_tableView->viewport()->width()/count;

    for(int i=0; i<count-1; i++)
        m_tableView->setColumnWidth(i, width);
    m_tableView->setColumnWidth(count-1, m_tableView->viewport()->width()-width*(count-1));

    m_tableView->resizeRowsToContents();
}

void CGui::loadEntry(const QModelIndex &index)
{
    if(!index.isValid())
    {
        debug("Invalid index");
        return;
    }

    CEntry *e = m_entries->getEntry(index);
    if(e==0)
        return;

    m_current = e;

    QString html = m_outGen->generate(e, m_entries->showhide());

    m_textBrowser->setHtml(html);

    #ifdef IS_MOBILE
        m_wnd->showBrowser();
    #endif
}

void CGui::setCurrentEntry(const QModelIndex &index)
{
    if(!index.isValid())
    {
        debug("Invalid index");
        return;
    }

    CEntry *e = m_entries->getEntry(index);
    if(e==0)
        return;

    m_current = e;
}

void CGui::loadEntry()
{
    if(!m_current)
    {
        if(m_entries->rowCount()==0)
        {
            m_textBrowser->setText(m_outGen->getEmpty());
            return;
        }

        loadEntry(m_entries->index(0, 0));
        m_tableView->selectRow(0);
        return;
    }

    QString html = m_outGen->generate(m_current, m_entries->showhide());

    m_textBrowser->setHtml(html);

    #ifdef IS_MOBILE
        m_wnd->showBrowser();
    #endif
}

void CGui::loadEntry(QString name)
{
    if(name.length()==0)
    {
        loadEntry();
        return;
    }

    int id = m_entries->findRowNumber(name);

    //debug("Current id: " + name + ". Current index: " + QString::number(id));

    if(id==-1)
    {
        loadEntry();
        return;
    }

    m_tableView->setCurrentIndex(m_entries->index(id, 0));
    loadEntry(m_entries->index(id, 0));
}

void CGui::addField()
{
    debug("addField()");

    CAddFieldDialog dialog(m_current, getWindow());
    if(dialog.exec()==QDialog::Rejected)
        return;
    //m_db->addField(dialog.entry_id(), dialog.name(), dialog.type(), dialog.value(), dialog.order());
    m_db->addField(dialog.map());

    QString current = m_current->id();
    m_current = 0;

    m_entries->refill();

#ifndef IS_MOBILE
    loadEntry(current);
#endif
}

void CGui::editField(int id)
{
    debug("editField()");

    debug("Editing field: " + m_current->getField(id)->id);
    CAddFieldDialog dialog(m_current, getWindow());
    dialog.setField(m_current->getField(id));
    if(dialog.exec()==QDialog::Rejected)
        return;
    //m_db->updateField(m_current->getField(id)->id, dialog.name(), dialog.type(), dialog.value(), dialog.order());
    m_db->updateField(dialog.map(), m_current->getField(id)->id);

    QString current = m_current->id();
    m_current = 0;

    m_entries->refill();

#ifndef IS_MOBILE
    loadEntry(current);
#endif
}

void CGui::deleteField(int id)
{
    debug("deleteField()");

    if(QMessageBox::question(getWindow(), tr("Question"), tr("Are you sure you want to delete this field?"),
                          QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes)
        return;

    if(!m_db->deleteField(m_current->getField(id)->id))
        return;

    m_current = 0;

    m_model->refill();
    m_entries->refill();
}

void CGui::saveImage(const QPixmap &img)
{
    QString path = QFileDialog::getSaveFileName(getWindow(), tr("Choose where to save image"), "",
                                                tr("PNG images (*.png)"));

    if(!path.length())
        return;

    debug("Path to save image: " + path);

    if(!img.save(path, "PNG"))
        critical(tr("Error"), tr("Can't open specified filename for writing"));
}

void CGui::saveFile(int id)
{
    QString path = QFileDialog::getSaveFileName(getWindow(), tr("Choose where to save file"), "",
                                                tr("Any file (*.*)"));

    if(!path.length())
        return;

    debug("Path to save file: " + path);

    QFile file(path);
    if(!file.open(QIODevice::WriteOnly))
    {
        critical(tr("Error"), tr("Can't open specified filename for writing"));
        return;
    }

    QByteArray data = m_current->getField(id)->value().toByteArray();

    qint64 bytes = file.write(data);

    if(bytes==0 && data.size()!=0)
    {
        critical(tr("Error"), tr("Can't open specified filename for writing"));
        return;
    }

    debug(QString::number(bytes) + " bytes written");
}

void CGui::start()
{
    if(m_set->value("database").isValid())
        if(QFile::exists(m_set->value("database").toString()))
        {
            loadDatabase(m_set->value("database").toString());
            return;
        }

    showWizard();
}

void CGui::showWizard()
{
    m_wnd->setEnabled(false);

    CStartDialog dialog(m_wnd);

    if(dialog.exec()==QDialog::Rejected)
    {
        if(!m_alreadyLoaded)
            close();
    }
    else
    {
        if(dialog.isCreate())
            createDatabase(dialog.filename(), dialog.cipher());
        else
            loadDatabase(dialog.filename());
    }

    m_wnd->setEnabled(true);
}

void CGui::showSync()
{
    m_wnd->setEnabled(false);
    CReplicationForm dialog(m_wnd);
    if(dialog.exec()==QDialog::Rejected)
    {
        m_wnd->setEnabled(true);
        return;
    }

    m_replicator->setLogin(dialog.login());
    m_replicator->setPassword(dialog.password());

    switch(dialog.action())
    {
        case CReplicationForm::Synchronize:
        {

            CSyncProcessDialog dialog(m_wnd);

            connect(m_replicator, SIGNAL(downloadProgress(qint64,qint64)), &dialog, SLOT(downloadProgress(qint64,qint64)));
            connect(m_replicator, SIGNAL(uploadProgress(qint64,qint64)), &dialog, SLOT(uploadProgress(qint64,qint64)));
            connect(m_replicator, SIGNAL(error(QNetworkReply::NetworkError)), &dialog, SLOT(error(QNetworkReply::NetworkError)));

            m_replicator->synchronize();
            dialog.exec();
            break;
        }
    }

    m_wnd->setEnabled(true);
}

void CGui::syncFinished(int code)
{
/*
define('DB_ERROR', 100);
define('UNSET_LOGIN_PASSWORD', 101);
define('UNREGISTRED_USER', 102);
define('UNSET_CHANGE_TYPE', 103);
define('WRONG_INPUT_DATA', 104);
define('WRONG_PROPS', 105);

define('ALL_OK', 400);
*/
    debug("Reply code: " + QString::number(code));
    switch(code)
    {
        case 200: QMessageBox::information(m_wnd, tr("Database error"), m_replicator->replyPhrase()); break;
        case 201: QMessageBox::information(m_wnd, tr("Unset login:password"), m_replicator->replyPhrase()); break;
        case 202: QMessageBox::information(m_wnd, tr("Unregistred user"), m_replicator->replyPhrase()); break;

        case 203:
        case 204: QMessageBox::information(m_wnd, tr("Wrong input data"), m_replicator->replyPhrase()); break;
        case 205: QMessageBox::information(m_wnd, tr("Wrong properties"), QString(m_replicator->replyData())); break;

        case 210: m_db->importChanges(m_replicator->replyData());
                m_db->setLastSync();
                m_model->refill();
                m_entries->refill();

                break;
    }
}

void CGui::showAbout()
{
    CAboutDialog dialog;
    dialog.exec();
}
